package com.mvvm.start.common

import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.os.Process
import android.text.TextUtils
import android.util.Log
import androidx.viewbinding.BuildConfig
import com.alibaba.android.arouter.launcher.ARouter
import com.google.auto.service.AutoService
import com.jeremyliao.liveeventbus.LiveEventBus
import com.mvvm.start.base.BaseApplication
import com.mvvm.start.base.R
import com.mvvm.start.base.app.ApplicationLifecycle
import com.mvvm.start.base.utils.*
import com.mvvm.start.common.mvvm.v.customview.CustomClassicsHeader
import com.mvvm.start.common.network.NetworkStateClient
import com.scwang.smart.refresh.footer.ClassicsFooter
import com.scwang.smart.refresh.layout.SmartRefreshLayout
import com.tencent.bugly.crashreport.CrashReport
import com.tencent.smtt.export.external.TbsCoreSettings
import com.tencent.smtt.sdk.QbSdk
import me.jessyan.autosize.AutoSize
import me.jessyan.autosize.AutoSizeConfig
import me.jessyan.autosize.onAdaptListener
import me.jessyan.autosize.unit.Subunits
import skin.support.SkinCompatManager
import skin.support.app.SkinAppCompatViewInflater
import skin.support.app.SkinCardViewInflater
import skin.support.constraint.app.SkinConstraintViewInflater
import skin.support.design.app.SkinMaterialViewInflater
import java.io.BufferedReader
import java.io.FileReader
import java.io.IOException
import java.util.*


/**
 * 项目相关的Application
 *
 * @author Qu Yunshuo
 * @since 4/16/21 3:37 PM
 */
@AutoService(ApplicationLifecycle::class)
class CommonApplication : ApplicationLifecycle {

    companion object {
        // 全局CommonApplication
        @SuppressLint("StaticFieldLeak")
        lateinit var mCommonApplication: CommonApplication
    }

    private fun initX5() {
        // 在调用TBS初始化、创建WebView之前进行如下配置
        var map = mutableMapOf<String, Any>()
        map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
        map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
        QbSdk.initTbsSettings(map)
        //搜集本地tbs内核信息并上报服务器，服务器返回结果决定使用哪个内核。
        val cb: QbSdk.PreInitCallback = object : QbSdk.PreInitCallback {
            override fun onViewInitFinished(arg0: Boolean) {
                //x5內核初始化完成的回调，为true表示x5内核加载成功，否则表示x5内核加载失败，会自动切换到系统内核。
                Log.d("app", " onViewInitFinished is $arg0")
            }
            override fun onCoreInitFinished() {
            }
        }
        //x5内核初始化接口
        QbSdk.initX5Environment(BaseApplication.application, cb)
    }

    /**
     * 设置全局SmartLayout风格
     */
    private fun initSmartLayout(): String {
        //设置全局的Header构建器
        SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout ->
            layout.setPrimaryColorsId(R.color.transparent, R.color.black) //全局设置主题颜色
            CustomClassicsHeader(context)
//            ClassicsHeader(context)
        }
        //设置全局的Footer构建器
        SmartRefreshLayout.setDefaultRefreshFooterCreator { context, layout ->
            //指定为经典Footer，默认是 BallPulseFooter
            ClassicsFooter(context).setDrawableSize(20f)
        }
        return "SmartLayout -->> init complete"
    }

    override fun onAttachBaseContext(context: Context) {
        mCommonApplication = this
    }

    override fun onCreate(application: Application) {}

    override fun onTerminate(application: Application) {}

    /**
     * 主线程前台初始化
     * @return MutableList<() -> String> 初始化方法集合
     */
    override fun initByFrontDesk(): MutableList<() -> String> {
        val list = mutableListOf<() -> String>()
        // 以下只需要在主进程当中初始化 按需要调整
        if (ProcessUtils.isMainProcess(BaseApplication.context)) {
                list.add { initBugly("") }
                list.add { initSkin() }
                list.add { initAutoSize(true) }
                list.add { initNetworkStateClient() }
                list.add { initSmartLayout() }
                list.add { initARouter() }
        }
        return list
    }

    private fun initARouter(): String {
        if (BuildConfig.DEBUG) {
            // 开启调试模式(如果在InstantRun模式下运行，必须开启调试模式！线上版本需要关闭,否则有安全风险)
            ARouter.openLog()   // 打印日志
            ARouter.openDebug()
        }
        ARouter.init(BaseApplication.application)
        return "ARouter -->> init complete"
    }

    /**
     * 不需要立即初始化的放在这里进行后台初始化
     */
    override fun initByBackstage() {
        initX5()
    }

    /**
     * 初始化网络状态监听客户端
     * @return Unit
     */
    private fun initNetworkStateClient(): String {
        NetworkStateClient.register()
        return "NetworkStateClient -->> init complete"
    }

    /**
     * 初始化换肤
     */
    private fun initSkin(): String {
        SkinCompatManager.withoutActivity(BaseApplication.application)
            .addInflater(SkinAppCompatViewInflater()) // 基础控件换肤初始化
            .addInflater(SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
            .addInflater(SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
            .addInflater(SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
            .setSkinStatusBarColorEnable(false) // 关闭状态栏换肤，默认打开[可选]
            .setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤，默认打开[可选]
            .loadSkin()
        return "Skin -->> init complete"
    }

    /**
     * 初始化 LiveEventBus
     */
    private fun initLiveEvent(): String {
        LiveEventBus.config().apply {
            lifecycleObserverAlwaysActive(true)
            autoClear(false)
        }
        return "LiveEventBus -->> init complete"
    }

    /**
     * 屏幕适配 初始化AutoSize
     * 是否使用主单位 640*360 主单位默认是宽做适配 最大宽度360dp 使用dp作为适配
     * 如果需要对单独界面 限制对高进行适配可以实现CustomAdapt接口 isBaseOnWidth返回false即可
     * 副单位是1920*1080 单位mm
     */
    private fun initAutoSize(isMainUnit: Boolean): String {
        if (isMainUnit) {
            AutoSize.initCompatMultiProcess(BaseApplication.context)
            /**
             * 以下是 AndroidAutoSize 可以自定义的参数, [AutoSizeConfig] 的每个方法的注释都写的很详细
             * 使用前请一定记得跳进源码，查看方法的注释, 下面的注释只是简单描述!!!
             */
            AutoSizeConfig.getInstance().apply {
                isCustomFragment = true
                onAdaptListener = object : onAdaptListener {
                    override fun onAdaptBefore(target: Any, activity: Activity) {
                        //使用以下代码, 可以解决横竖屏切换时的屏幕适配问题
                        //使用以下代码, 可支持 Android 的分屏或缩放模式, 但前提是在分屏或缩放模式下当用户改变您 App 的窗口大小时
                        //系统会重绘当前的页面, 经测试在某些机型, 某些情况下系统不会主动重绘当前页面, 所以这时您需要自行重绘当前页面
                        //ScreenUtils.getScreenSize(activity) 的参数一定要不要传 Application!!!
                        //                        AutoSizeConfig.getInstance().setScreenWidth(ScreenUtils.getScreenSize(activity)[0]);
                        //                        AutoSizeConfig.getInstance().setScreenHeight(ScreenUtils.getScreenSize(activity)[1]);

                        String.format(Locale.ENGLISH, "%s onAdaptBefore!", target.javaClass.name)
                            .loge("AutoSizeLog")
                    }

                    override fun onAdaptAfter(target: Any, activity: Activity) {
                        String.format(Locale.ENGLISH, "%s onAdaptAfter!", target.javaClass.name)
                            .loge("AutoSizeLog")
                    }
                }
                //是否使用设备的实际尺寸做适配, 默认为 false, 如果设置为 false, 在以屏幕高度为基准进行适配时
                //AutoSize 会将屏幕总高度减去状态栏高度来做适配
                //设置为 true 则使用设备的实际屏幕高度, 不会减去状态栏高度
                //在全面屏或刘海屏幕设备中, 获取到的屏幕高度可能不包含状态栏高度, 所以在全面屏设备中不需要减去状态栏高度，所以可以 setUseDeviceSize(true)
                isUseDeviceSize = false
                //是否打印 AutoSize 的内部日志, 默认为 true, 如果您不想 AutoSize 打印日志, 则请设置为 false
                setLog(true)
                //是否全局按照宽度进行等比例适配, 默认为 true, 如果设置为 false, AutoSize 会全局按照高度进行适配
                isBaseOnWidth = true
                //设置屏幕适配逻辑策略类, 一般不用设置, 使用框架默认的就好
//            setAutoAdaptStrategy { target, activity ->
//            }
            }
        } else {
            //AndroidAutoSize 默认开启对 dp 的支持, 调用 UnitsManager.setSupportDP(false); 可以关闭对 dp 的支持
            //主单位 dp 和 副单位可以同时开启的原因是, 对于旧项目中已经使用了 dp 进行布局的页面的兼容
            //让开发者的旧项目可以渐进式的从 dp 切换到副单位, 即新页面用副单位进行布局, 然后抽时间逐渐的将旧页面的布局单位从 dp 改为副单位
            //最后将 dp 全部改为副单位后, 再使用 UnitsManager.setSupportDP(false); 将 dp 的支持关闭, 彻底隔离修改 density 所造成的不良影响
            //如果项目完全使用副单位, 则可以直接以像素为单位填写 AndroidManifest 中需要填写的设计图尺寸, 不需再把像素转化为 dp
            AutoSizeConfig.getInstance().unitsManager.apply {
                isSupportDP = true
                supportSubunits = Subunits.MM
            }
        }


        return "AutoSize -->> init complete"


    }

    /**
     * 初始化bugly
     */
    private fun initBugly(appID: String?): String {
        val packageName = BaseApplication.context.packageName // 获取当前进程名
        val processName = getProcessName(Process.myPid())
        val strategy = CrashReport.UserStrategy(BaseApplication.context)
        strategy.isUploadProcess = processName == null || processName == packageName
        strategy.appChannel = getChannelName()  //设置渠道
        strategy.appVersion = CommonUtils.getAppVersionName()      //App的版本
        strategy.appPackageName = packageName  //App的包名
        if (CommonUtils.getUserID().isNotEmpty()) {
            CrashReport.setUserId(CommonUtils.getUserID()) //该用户本次启动后的异常日志用户ID都将是9527
        }
        CrashReport.setIsDevelopmentDevice(BaseApplication.context, BuildConfig.DEBUG)
        CrashReport.initCrashReport(BaseApplication.context, appID, true, strategy)
        return "Bugly -->> init complete"

    }

    /**
     * 获取进程号对应的进程名
     *
     * @param pid 进程号
     * @return 进程名
     */
    private fun getProcessName(pid: Int): String? {
        lateinit var reader: BufferedReader
        try {
            reader = BufferedReader(FileReader("/proc/$pid/cmdline"))
            var processName = reader.readLine()
            if (!TextUtils.isEmpty(processName)) {
                processName = processName.trim { it <= ' ' }
            }
            return processName
        } catch (throwable: Throwable) {
            throwable.printStackTrace()
        } finally {
            try {
                reader?.close()
            } catch (exception: IOException) {
                exception.printStackTrace()
            }
        }
        return null
    }

    /**
     * 获取友盟渠道名
     * @return 如果没有获取成功，那么返回值为空
     */
    fun getChannelName(): String {
        var channelName = "1"
        try {
            val packageManager = BaseApplication.application.packageManager
            if (packageManager != null) {
                //注意此处为ApplicationInfo 而不是 ActivityInfo,因为友盟设置的meta-data是在application标签中，而不是某activity标签中，所以用ApplicationInfo
                val applicationInfo =
                    packageManager.getApplicationInfo(
                        BaseApplication.application.packageName,
                        PackageManager.GET_META_DATA
                    )
                if (applicationInfo != null) {
                    if (applicationInfo.metaData != null) {
                        //此处这样写的目的是为了在debug模式下也能获取到渠道号，如果用getString的话只能在Release下获取到。
                        channelName = applicationInfo.metaData["UMENG_CHANNEL"].toString() + ""
                    }
                }
            }
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
        return channelName
    }
}